﻿using System;
using System.Collections;
using System.Collections.Generic;

namespace SDL
{
    public class ListChangedEventArgs : EventArgs
    {
        public Object[] NewItems { get; private set; }
        public Object[] OldItems { get; private set; }

        public ListChangedEventArgs(Object newItem, Object oldItem)
        {
            NewItems = null;
            if( newItem != null )
            {
                NewItems = new Object[1];
                NewItems[0] = newItem;
            }

            OldItems = null;
            if( oldItem != null )
            {
                OldItems = new Object[1];
                OldItems[0] = oldItem;
            }
        }

        public ListChangedEventArgs(Object[] newItems, Object[] oldItems)
        {
            NewItems = newItems;
            OldItems = oldItems;
        }
    }

    public delegate void ListChangedEventHandler(object sender,
                                                 ListChangedEventArgs e);

    public sealed class ObservableList<T> : IList<T>, IList where T : class
    {
        /////////////////////////////////////////////////////////////////////////////
        // Public Events

        public event ListChangedEventHandler ListChanged;

        /////////////////////////////////////////////////////////////////////////////
        // Public Properties

        public int Count { get { return _count; } }
        public T this[int index]
        {
            get { return _list[index]; }
            set
            {
                // TODO: UNTESTED

                // save the old item at this index
                T oldItem = _list[index];

                // set the new item at this index
                _list[index] = value;

                // raise the list changed event with the old and new items
                OnListChanged(new ListChangedEventArgs(value, oldItem));
            }
        }

        /////////////////////////////////////////////////////////////////////////////
        // Default Constructor

        public ObservableList()
        {
            _count = 0;
            _list = new T[1];
        }

        /////////////////////////////////////////////////////////////////////////////
        // Public Methods

        public int Add(T item)
        {
            // TODO: UNTESTED

            // validate arguments
            if( item == null )
            {
                throw new ArgumentNullException("item");
            }

            // make sure there is enough space in the list
            if( _count == _list.Length )
            {
                // save the old list and create a new one
                T[] oldList = _list;
                _list = new T[_count * 2];

                // copy the old list to the new one
                for( int i = 0; i < _count; ++i )
                {
                    _list[i] = oldList[i];
                }
            }

            // append the new item to the list
            _list[_count] = item;
            _count += 1;

            // raise the list changed event with the new item
            OnListChanged(new ListChangedEventArgs(item, null));

            // return the index at which the item was added
            return _count - 1;
        }

        public void Insert(int index, T item)
        {
            // TODO: UNTESTED

            // validate arguments
            if( index < 0 || index > _count )
            {
                throw new ArgumentOutOfRangeException("index");
            }
            if( item == null )
            {
                throw new ArgumentNullException("item");
            }

            // make a new list if there is not enough space in the old one
            if( _count == _list.Length )
            {
                // save the old list and create a new one
                T[] oldList = _list;
                _list = new T[_count * 2];

                // copy the old list to the new one while inserting the new item
                for( int i = 0, j = 0; i < _count; ++i, ++j )
                {
                    if( i == index )
                    {
                        _list[j] = item;
                        j += 1;
                    }
                    _list[j] = oldList[i];
                }
                _count += 1;
            }
            else
            {
                // move the end of the list forward and increase the count
                for( int i = _count; i > index; --i )
                {
                    _list[i] = _list[i - 1];
                }
                _count += 1;

                // insert the new item into the list
                _list[index] = item;
            }

            // raise the list changed event with the new item
            OnListChanged(new ListChangedEventArgs(item, null));
        }

        public bool Remove(T item)
        {
            // TODO: UNTESTED

            // validate arguments
            if( item == null )
            {
                throw new ArgumentNullException("item");
            }

            // find the index of the item
            int index = 0;
            for( ; index < _count; ++index )
            {
                if( _list[index] == item ) break;
            }

            // if the index reached the count, the remove failed
            if( index == _count ) return false;

            // reduce the count and push the list back
            _count -= 1;
            for( int i = index; i < _count; ++i )
            {
                _list[i] = _list[i + 1];
            }

            // raise the list changed event with the old item
            OnListChanged(new ListChangedEventArgs(null, item));

            // success
            return true;
        }

        public void RemoveAt(int index)
        {
            // TODO: UNTESTED

            // validate arguments
            if( index < 0 || index >= _count )
            {
                throw new ArgumentOutOfRangeException("index");
            }

            // save the item
            T oldItem = _list[index];

            // reduce the count and push the list back
            _count -= 1;
            for( int i = index; i < _count; ++i )
            {
                _list[i] = _list[i + 1];
            }

            // raise the list changed event with the old item
            OnListChanged(new ListChangedEventArgs(null, oldItem));
        }

        public void Clear()
        {
            // TODO: UNTESTED

            // save the old list
            T[] oldList = new T[_count];
            for( int i = 0; i < _count; ++i )
            {
                oldList[i] = _list[i];
            }

            // create a new list
            _list = new T[1];
            _count = 0;

            // raise the list changed event with the old list
            OnListChanged(new ListChangedEventArgs(null, oldList));
        }

        public void Move(int oldIndex, int newIndex)
        {
            // TODO: UNTESTED

            // validate arguments
            if( oldIndex < 0 || oldIndex >= _count )
            {
                throw new ArgumentOutOfRangeException("oldIndex");
            }
            if( newIndex < 0 || newIndex >= _count )
            {
                throw new ArgumentOutOfRangeException("newIndex");
            }

            // save the item
            T item = _list[oldIndex];

            if( newIndex > oldIndex )
            {
                // move all middle items back
                for( int i = oldIndex; i < newIndex - 1; ++i )
                {
                    _list[i] = _list[i + 1];
                }
            }
            else if( newIndex < oldIndex )
            {
                // move all middle items forward
                for( int i = oldIndex; i > newIndex; --i )
                {
                    _list[i] = _list[i - 1];
                }
            }

            // insert the item in it's new place
            _list[newIndex] = item;
        }

        public bool Move(T item, int newIndex)
        {
            // TODO: UNTESTED

            // validate arguments
            if( item == null )
            {
                throw new ArgumentNullException("item");
            }
            if( newIndex < 0 || newIndex >= _count )
            {
                throw new ArgumentOutOfRangeException("newIndex");
            }

            // find the index of the item
            int oldIndex = 0;
            for( ; oldIndex < _count; ++oldIndex )
            {
                if( item == _list[oldIndex] ) break;
            }

            // make sure the item was found
            if( oldIndex == _count ) return false;

            if( newIndex > oldIndex )
            {
                // move all middle items back
                for( int i = oldIndex; i < newIndex - 1; ++i )
                {
                    _list[i] = _list[i + 1];
                }
            }
            else if( newIndex < oldIndex )
            {
                // move all middle items forward
                for( int i = oldIndex; i > newIndex; --i )
                {
                    _list[i] = _list[i - 1];
                }
            }

            // insert the item in it's new place
            _list[newIndex] = item;

            // success
            return true;
        }

        public bool Contains(T item)
        {
            // TODO: UNTESTED

            // validate arguments
            if( item == null )
            {
                throw new ArgumentNullException("item");
            }

            for( int i = 0; i < _count; ++i )
            {
                if( item == _list[i] )
                {
                    return true;
                }
            }
            return false;
        }

        public int IndexOf(T item)
        {
            // TODO: UNTESTED

            // validate arguments
            if( item == null )
            {
                throw new ArgumentNullException("item");
            }

            for( int i = 0; i < _count; ++i )
            {
                if( item == _list[i] )
                {
                    return i;
                }
            }
            return -1;
        }

        public void CopyTo(Array array)
        {
            // TODO: UNTESTED

            // validate arguments
            if( array == null )
            {
                throw new ArgumentNullException("array");
            }

            for( int i = 0; i < _count; ++i )
            {
                array.SetValue(_list[i], i);
            }
        }

        public void CopyTo(Array array, int index)
        {
            // TODO: UNTESTED

            // validate arguments
            if( array == null )
            {
                throw new ArgumentNullException("array");
            }
            if( index < 0 || index >= array.Length - _count )
            {
                throw new ArgumentOutOfRangeException("index");
            }

            for( int i = 0; i < _count; ++i )
            {
                array.SetValue(_list[i], index + i);
            }
        }

        public IEnumerator<T> GetEnumerator()
        {
            // TODO: UNTESTED

            for( int i = 0; i < _count; ++i )
            {
                yield return _list[i];
            }
        }

        public ObservableList<T> Copy()
        {
            return new ObservableList<T>(this);
        }

        /////////////////////////////////////////////////////////////////////////////
        // Private Constructor

        private ObservableList(ObservableList<T> list)
        {
            if( list.Count > 0 )
            {
                _count = list.Count;
                _list = new T[_count];
                for( int i = 0; i < _count; ++i )
                {
                    _list[i] = list[i];
                }
            }
            else
            {
                _count = 0;
                _list = new T[1];
            }
        }

        /////////////////////////////////////////////////////////////////////////////
        // Private Methods

        private void OnListChanged(ListChangedEventArgs e)
        {
            ListChangedEventHandler handler = ListChanged;
            if( handler != null )
            {
                handler(this, e);
            }
        }

        /////////////////////////////////////////////////////////////////////////////
        // Private Data

        int _count;
        T[] _list;

        /////////////////////////////////////////////////////////////////////////////
        // IList<T> Properties

        T IList<T>.this[int index]
        {
            get { return this[index]; }
            set { this[index] = value; }
        }

        /////////////////////////////////////////////////////////////////////////////
        // IList<T> Methods

        int IList<T>.IndexOf(T item)
        {
            return IndexOf(item);
        }

        void IList<T>.Insert(int index, T item)
        {
            Insert(index, item);
        }

        void IList<T>.RemoveAt(int index)
        {
            RemoveAt(index);
        }

        /////////////////////////////////////////////////////////////////////////////
        // IList Properties

        bool IList.IsFixedSize { get { return false; } }
        bool IList.IsReadOnly { get { return false; } }
        Object IList.this[int index]
        {
            get { return this[index]; }
            set { this[index] = value as T; }
        }

        /////////////////////////////////////////////////////////////////////////////
        // IList Methods

        int IList.Add(Object value)
        {
            return Add(value as T);
        }

        void IList.Clear()
        {
            Clear();
        }

        bool IList.Contains(Object value)
        {
            return Contains(value as T);
        }

        int IList.IndexOf(Object value)
        {
            return IndexOf(value as T);
        }

        void IList.Insert(int index, Object value)
        {
            Insert(index, value as T);
        }

        void IList.Remove(Object value)
        {
            Remove(value as T);
        }

        void IList.RemoveAt(int index)
        {
            RemoveAt(index);
        }

        /////////////////////////////////////////////////////////////////////////////
        // ICollection<T> Properties

        int ICollection<T>.Count { get { return Count; } }
        bool ICollection<T>.IsReadOnly { get { return false; } }

        /////////////////////////////////////////////////////////////////////////////
        // ICollection<T> Methods

        void ICollection<T>.Add(T item)
        {
            Add(item);
        }

        void ICollection<T>.Clear()
        {
            Clear();
        }

        bool ICollection<T>.Contains(T item)
        {
            return Contains(item);
        }

        void ICollection<T>.CopyTo(T[] array, int arrayIndex)
        {
            CopyTo(array, arrayIndex);
        }

        bool ICollection<T>.Remove(T item)
        {
            return Remove(item);
        }

        /////////////////////////////////////////////////////////////////////////////
        // ICollection Properties

        int ICollection.Count { get { return Count; } }
        bool ICollection.IsSynchronized { get { return false; } }
        Object ICollection.SyncRoot { get { return null; } }

        /////////////////////////////////////////////////////////////////////////////
        // ICollection Methods

        void ICollection.CopyTo(Array array, int index)
        {
            CopyTo(array, index);
        }

        /////////////////////////////////////////////////////////////////////////////
        // IEnumerable<T> Methods

        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            return GetEnumerator();
        }

        /////////////////////////////////////////////////////////////////////////////
        // IEnumerable Methods

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}
